# -*- coding: utf-8 -*-
# Project : flacon
# File : trackset.py
# (c) Alexander Sokolov sokoloff.a@gmail.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.


from settings import *
from flaconglobal import *
from track import *
from chardet.universaldetector import UniversalDetector

from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *

from errors import *

class TrackSet(QObject):
    chardetToQtCodecs = {
        #'2143-ORDERED'  : '',
        #'3412-ORDERED'  : '',
        #'ASCII'         : '',
        'BIG5'          : 'Big5',
        'EUC-JP'        : 'EUC-JP',
        'EUC-KR'        : 'EUC-KR',
        #'EUC-TW'        : '',
        'GB18030'       : 'GB18030-0',
        #'GB2312'        : '',
        #'HZ-GB-2312'    : '',
        #'IBM855'        : '',
        'IBM866'        : 'IBM 866',
        #'ISO-2022-CN'   : '',
        'ISO-2022-JP'   : 'ISO 2022-JP',
        #'ISO-2022-KR'   : '',
        'ISO-8859-2'    : 'ISO 8859-2',
        'ISO-8859-5'    : 'ISO 8859-5',
        'ISO-8859-7'    : 'ISO 8859-7',
        'ISO-8859-8'    : 'ISO 8859-8',
        'KOI8-R'        : 'KOI8-R',
        'MACCYRILLIC'   : 'Apple Roman',
        'SHIFT_JIS'     : 'JIS X 0208',
        'TIS-620'       : 'TIS-620',
        'UTF-16BE'      : 'UTF-16BE',
        'UTF-16LE'      : 'UTF-16LE',
        'UTF-32BE'      : 'UTF-32BE',
        'UTF-32LE'      : 'UTF-32LE',
        'UTF-8'         : 'UTF-8',
        'WINDOWS-1250'  : 'Windows-1250',
        'WINDOWS-1251'  : 'Windows-1251',
        'WINDOWS-1252'  : 'Windows-1252',
        'WINDOWS-1253'  : 'Windows-1253',
        'WINDOWS-1254'  : 'Windows-1254',
        'WINDOWS-1255'  : 'Windows-1255',
    }


   #****************************************************************
   #
   #****************************************************************
    def __init__(self, uri = ''):
        QObject.__init__(self)
        self._tracks = []
        self._startTrackNum = 1
        self._discId = QString()
        self._codePage = settings.value("Tags/DefaultCodepage").toString().toLatin1()
        self._textCodec = None
        self._textCodecDetected = False
        self._uri = QString(uri)
        self._disc = None
        self._fileTag = QString()
        self._title = QString()
        self._titleEncoded = True
        self._catalog = QString()
        self._cdTextFile = QString()


   #****************************************************************
   #
   #****************************************************************
    def uri(self):
        return self._uri


   #****************************************************************
   #
   #****************************************************************
    def setUri(self, value):
        self._uri = QString(value)


   #****************************************************************
   #
   #****************************************************************
    def title(self):
        if self._titleEncoded:
            return self._title
        else:
            return self.decode(self._title)


   #****************************************************************
   #
   #****************************************************************
    def setTitle(self, value, encoded = True):
        self._title = QString(value)
        self._titleEncoded = encoded


   #****************************************************************
   #
   #****************************************************************
    def catalog(self):
        return self._catalog


   #****************************************************************
   #
   #****************************************************************
    def setCatalog(self, value):
        self._catalog = value


   #****************************************************************
   #
   #****************************************************************
    def cdTextFile(self):
        return self._cdTextFile


   #****************************************************************
   #
   #****************************************************************
    def setCdTextFile(self, value):
        self._cdTextFile = value


   #****************************************************************
   #
   #****************************************************************
    def decode(self, value):
        if not isinstance(value, QString):
            return value

        if not self._textCodecDetected:
            self._textCodec = self._createTextCodec()

        if self._textCodec:
            return self._textCodec.toUnicode(value.toAscii())
        else:
            return value


   #****************************************************************
   #
   #****************************************************************
    def _createTextCodec(self):
        self._textCodecDetected = True

        if self._codePage != QString("AUTODETECT"):
            return QTextCodec.codecForName(QString(self._codePage).toLatin1())


        # Auto detect codepage ...........................
        detector = UniversalDetector()
        for track in self._tracks:
            detector.feed(track.dataForAutoDetect())

        detector.close()
        codePage = detector.result['encoding']
        if not codePage:
            return None

        try:
            qtcp = self.chardetToQtCodecs[codePage.upper()]
            return QTextCodec.codecForName(QString(qtcp).toLatin1())

        except KeyError, e:
            return None


   #****************************************************************
   #
   #****************************************************************
    def isActive(self):
        return (self._disc and self._disc.tracks() == self)


   #****************************************************************
   #
   #****************************************************************
    def activate(self):
        if self._disc:
            self._disc.selectTrackSet(self)


   #****************************************************************
   #
   #****************************************************************
    def trackDataChanged(self, track):
        if self._disc and self._disc.tracks() == self:
            self._disc.trackDataChanged(self.indexOf(track))


   #****************************************************************
   #
   #****************************************************************
    def __getitem__(self, index):
        return self._tracks[index]


   #****************************************************************
   #
   #****************************************************************
    def newTrack(self):
        track = Track(self)
        self._tracks.append(track)
        return track


   #****************************************************************
   #
   #****************************************************************
    def startTrackNum(self):
        return self._startTrackNum


   #****************************************************************
   #
   #****************************************************************
    def setStartTrackNum(self, value):
        self._startTrackNum = value
        for track in self._tracks:
            track.setStartTrackNum(value)


   #****************************************************************
   #
   #****************************************************************
    def count(self):
        return len(self._tracks)


   #****************************************************************
   #
   #****************************************************************
    def indexOf(self, track):
        try:
            return self._tracks.index(track)
        except:
            return -1


   #****************************************************************
   #
   #****************************************************************
    def codePage(self):
        return self._codePage


   #****************************************************************
   #
   #****************************************************************
    def setCodePage(self, value):
        if self._codePage == value:
            return

        self._codePage = QString(value)
        self._textCodec = None
        self._textCodecDetected = False

        for i in xrange(0, self.count()):
            self.trackDataChanged(i)


   #****************************************************************
   #
   #****************************************************************
    def realCodePage(self):
        if self._textCodec:
            return self._textCodec.name()
        else:
            return ""


   #****************************************************************
   #
   #****************************************************************
    def discId(self):
        return self._discId


   #****************************************************************
   #
   #****************************************************************
    def setDiscId(self, value):
        self._discId = value
        for i in xrange(0, self.count()):
            self.trackDataChanged(i)


   #****************************************************************
   #
   #****************************************************************
    def disc(self):
        return self._disc


   #****************************************************************
   #
   #****************************************************************
    def setDisc(self, value):
        self._disc = value


   #****************************************************************
   #
   #****************************************************************
    def fileTag(self):
        return self._fileTag


class CueTrackSet(TrackSet):
   #****************************************************************
   #
   #****************************************************************
    def __init__(self, fileName):
        TrackSet.__init__(self)
        self._uri = QFileInfo(fileName).canonicalFilePath()

        FileError.checkFile(fileName)
        f = QFile(fileName)

        self._title = QFileInfo(f).fileName()

        if f.open(QIODevice.ReadOnly | QIODevice.Text):
            self._cueData = QStringList()

            while not f.atEnd():
                self._cueData.append(QString(f.readLine()))
            f.close()

            if not len(self._cueData):
                raise FileErrorBadCue(fileName)

            #****************************************************
            # Delete BOM
            # see http://en.wikipedia.org/wiki/Byte_Order_Mark
            #****************************************************
            if (self._cueData[0].startsWith("\xEF\xBB\xBF")):   # UTF-8
                self.setCodePage('UTF-8')
                self._cueData[0] = self._cueData[0].remove(0, 3)

            if (self._cueData[0].startsWith("\xFE\xFF")):       # UTF-16 (BE)
                self.setCodePage('UTF-16BE')
                self._cueData[0] = self._cueData[0].remove(0, 2)

            if (self._cueData[0].startsWith("\xFF\xFE")):       # UTF-16 (LE)
                self.setCodePage('UTF-16LE')
                self._cueData[0] = self._cueData[0].remove(0, 2)

            self.parseCue(self._cueData)

            if not self.count():
                raise FileErrorBadCue(fileName)
        else:
            raise


   #****************************************************************
   #
   #****************************************************************
    def cueData(self):
        return self._cueData


   #****************************************************************
   #
   #****************************************************************
    def parseCue(self, lines):
        track  = None
        album  = QString()
        performer = QString()
        genre  = QString()
        discId = QString()
        songWriter = QString()
        year   = 0

        for i in xrange(0, lines.count()):
            line = lines[i].trimmed()

            if (line.isEmpty()): next
            line = line.trimmed()

            tag = line.section(' ', 0, 0).toUpper()
            value = line.section(' ', 1)
            if (value.length()>2 and (value[0] == "'" or value[0] == '"')):
                value = value.mid(1, value.length()-2)

            #==============================================
            if (tag == "TRACK"):
                num = value.section(' ', 0, 0)
                track = self.newTrack()
                track.setTrackNum(int(num))
                track.setArtist(performer, False)
                track.setSongWriter(songWriter, False)
                track.setAlbum(album, False)
                track.setGenre(genre, False)
                track.setYear(year)
                self.setDiscId(discId)

            #==============================================
            elif (tag == "TITLE"):
                if (track):
                    track.setTitle(value, False)
                else:
                    album = QString(value)

            #==============================================
            elif (tag == "PERFORMER"):
                if (track):
                    track.setArtist(value, False)
                else:
                    performer = QString(value)

            #==============================================
            elif (tag == "SONGWRITER"):
                if (track):
                    track.setSongWriter(value, False)
                else:
                    songWriter = QString(value)

            #==============================================
            elif (tag == "DATE"):           # For incorrect CUE files
                if (track):
                    track.setYear(value, False)
                else:
                    year = value

            #==============================================
            elif (tag == "FILE"):
                if not track:
                    self._fileTag = QString(value[:-5])
                else:
                    raise FileErrorMultiFileTag(self._uri)

            #==============================================
            elif (tag == "INDEX"):
                if track:
                    indexNum = int(value.section(' ', 0, 0))
                    time = value.section(' ', 1)

                    if indexNum == 0:
                        track.setCueIndex0(TrackIndex(time))

                    elif indexNum == 1:
                        track.setCueIndex1(TrackIndex(time))

            #==============================================
            elif (tag == "CATALOG"):
                self.setCatalog(value)

            #==============================================
            elif (tag == "CDTEXTFILE"):
                self.setCdTextFile(value)

            #==============================================
            elif (tag == "FLAGS"):
                if (track):
                    track.setFlags(value, False)

            #==============================================
            elif (tag == "ISRC"):
                if (track):
                    track.setIsrc(value, False)

            #==============================================
            elif (tag == "REM"):
                subTag = value.section(' ', 0, 0).toUpper()
                value = value.section(' ', 1)
                if (value.length()>2 and (value[0] == "'" or value[0] == '"')):
                    value = value.mid(1, value.length()-2)

                if (not track):
                    if (subTag == 'DATE'):   year   = value
                    if (subTag == 'GENRE'):  genre  = value
                    if (subTag == 'DISCID'): discId = value


